fix: resolve O(n^3) performance degradation in workflow tester (#377) - #492
Open
denisaditya0 wants to merge 1 commit into
Open
fix: resolve O(n^3) performance degradation in workflow tester (#377)#492denisaditya0 wants to merge 1 commit into
denisaditya0 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #377
Problem
The workflow tester exhibits O(n^3) performance degradation when using
workflow.Gowith many concurrent goroutines. A workflow spawning 256 goroutines takes ~71 seconds in the tester vs milliseconds with a real backend.Root Cause
Two compounding factors:
Executor recreation on every task — The tester created a new executor for each workflow task, forcing full history replay. With n activities completing one at a time, this means n replays of growing history (O(n^2) total replay work).
Linear scan in CommandByScheduleEventID — During replay, each event lookup scans all commands linearly. With O(n) events each scanning O(n) commands, this adds another factor of n, yielding O(n^3) overall.
Changes
1. Cache executor per workflow instance in tester (
tester/tester.go)The real worker already caches executors via an LRU cache. The tester now does the same — stores the executor on the
testWorkflowstruct and reuses it across tasks. The executor is closed when the workflow finishes to prevent goroutine leaks.2. O(1) command lookup via index map (
internal/workflowstate/workflowstate.go)Added a
commandIndex map[int64]command.Commandalongside the existing slice.AddCommandpopulates both, andCommandByScheduleEventIDnow returns directly from the map instead of scanning the slice.Benchmark Results
Using the exact reproduction from #377 (n goroutines, each executing one activity):
At n=256: 8.5 GB heap allocations reduced to 50 MB per operation (measured via
-benchmem).Raw benchmark output
Before (main):
After (this PR):
Remaining O(n^2) behavior
The coroutine scheduler (
internal/sync/scheduler.go) iterates all coroutines on everyContinue()call. This is inherent to the cooperative scheduling model and would require a more invasive redesign to address. However, with the cubic factor eliminated, the tester is now practical for real-world use cases (256 goroutines in 500ms vs 71 seconds).Testing
tester/tester_bench_test.go